package com.xbk.seckill.order.application.service.impl;

import cn.hutool.core.util.NumberUtil;
import com.xbk.seckill.common.cache.distribute.DistributedCacheService;
import com.xbk.seckill.common.cache.local.LocalCacheService;
import com.xbk.seckill.common.cache.model.SeckillBusinessCache;
import com.xbk.seckill.common.constants.SeckillConstants;
import com.xbk.seckill.common.exception.ErrorCode;
import com.xbk.seckill.common.exception.SeckillException;
import com.xbk.seckill.common.lock.DistributedLock;
import com.xbk.seckill.common.lock.factory.DistributedLockFactory;
import com.xbk.seckill.dubbo.interfaces.goods.SeckillGoodsDubboService;
import com.xbk.seckill.dubbo.interfaces.stock.SeckillStockDubboService;
import com.xbk.seckill.mq.MessageSenderService;
import com.xbk.seckill.order.application.model.task.SeckillOrderTask;
import com.xbk.seckill.order.application.service.PlaceOrderTaskService;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;

import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 订单任务服务实现类
 *
 * @author xiexu
 * @date 2025/3/2 19:19
 */
@Slf4j
@Service
@ConditionalOnProperty(name = "submit.order.type", havingValue = "async")
public class PlaceOrderTaskServiceImpl implements PlaceOrderTaskService {

    @Autowired
    private MessageSenderService messageSenderService;

    @Autowired
    private LocalCacheService<Long, Integer> localCacheService;

    @Autowired
    private DistributedCacheService distributedCacheService;

    @Autowired
    private DistributedLockFactory distributedLockFactory;

    @DubboReference(version = "1.0.0", check = false)
    private SeckillStockDubboService seckillStockDubboService;

    @DubboReference(version = "1.0.0", check = false)
    private SeckillGoodsDubboService seckillGoodsDubboService;

    @Value("${submit.order.token.multiple:1.5}")
    private Double multiple;

    @Value("${place.order.type:lua}")
    private String placeOrderType;

    // 可重入锁
    private Lock lock = new ReentrantLock();

    /**
     * 提交订单任务
     *
     * @param seckillOrderTask
     * @return
     */
    @Override
    public boolean submitOrderTask(SeckillOrderTask seckillOrderTask) {
        if (Objects.isNull(seckillOrderTask)) {
            throw new SeckillException(ErrorCode.PARAMS_INVALID);
        }
        // 任务key
        String taskKey = SeckillConstants.getKey(SeckillConstants.ORDER_TASK_ID_KEY, seckillOrderTask.getOrderTaskId());
        // 检测是否已经执行过该订单任务
        Long result = distributedCacheService.checkExecute(taskKey, SeckillConstants.ORDER_TASK_EXPIRE_SECONDS);
        // 已经执行过该订单任务
        if (NumberUtil.equals(result, SeckillConstants.CHECK_RECOVER_STOCK_HAS_EXECUTE)) {
            throw new SeckillException(ErrorCode.REDUNDANT_SUBMIT);
        }
        // 通过商品id，获取可用的下单许可数量
        Long goodsId = seckillOrderTask.getSeckillOrderCommand().getGoodsId();
        Integer availableOrderTokens = this.getAvailableOrderTokens(goodsId);
        // 不存在下单许可，或下单许可被用完
        if (availableOrderTokens == null || availableOrderTokens <= 0) {
            throw new SeckillException(ErrorCode.ORDER_TOKENS_NOT_AVAILABLE);
        }
        // 未获取到下单许可
        if (!this.takeOrderToken(goodsId)) {
            log.info("submitOrderTask|获取下单许可失败|{},{}", seckillOrderTask.getUserId(), seckillOrderTask.getOrderTaskId());
            throw new SeckillException(ErrorCode.ORDER_TOKENS_NOT_AVAILABLE);
        }
        // 发送消息（topic：topic_order_msg）
        boolean sendSuccess = messageSenderService.send(seckillOrderTask);
        if (!sendSuccess) {
            log.info("submitOrderTask|下单任务提交失败|{},{}", seckillOrderTask.getUserId(), seckillOrderTask.getOrderTaskId());
            // 恢复下单许可
            this.recoverOrderToken(goodsId);
            // 清除是否被执行过的数据
            distributedCacheService.delete(taskKey);
        }
        return sendSuccess;
    }

    /**
     * 获取下单许可，需要重点理解为何要循环三次处理业务
     */
    private boolean takeOrderToken(Long goodsId) {
        // 循环三次获取下单许可
        for (int i = 0; i < 3; i++) {
            // 尝试从分布式缓存中获取下单许可
            String orderTokenKey = SeckillConstants.getKey(SeckillConstants.ORDER_TASK_AVAILABLE_TOKENS_KEY, String.valueOf(goodsId));
            Long result = distributedCacheService.takeOrderToken(orderTokenKey);
            // 如果返回的结果为空，则直接返回false
            if (Objects.isNull(result)) {
                return false;
            }
            if (result == SeckillConstants.LUA_RESULT_NOT_EXECUTE) {
                // 刷新缓存的下单许可
                this.refreshLatestAvailableTokens(goodsId, false);
                // 跳过本次循环
                continue;
            }
            return result == SeckillConstants.LUA_RESULT_EXECUTE_TOKEN_SUCCESS;
        }
        return false;
    }

    /**
     * 恢复下单许可
     */
    private boolean recoverOrderToken(Long goodsId) {
        // 尝试恢复下单许可
        Long result = distributedCacheService.recoverOrderToken(SeckillConstants.getKey(SeckillConstants.ORDER_TASK_AVAILABLE_TOKENS_KEY, String.valueOf(goodsId)));
        // 如果返回为空，则直接返回false
        if (result == null) {
            return false;
        }
        // 刷新下单许可
        if (result == SeckillConstants.LUA_RESULT_NOT_EXECUTE) {
            this.refreshLatestAvailableTokens(goodsId, false);
            return true;
        }
        return result == SeckillConstants.LUA_RESULT_EXECUTE_TOKEN_SUCCESS;
    }

    /**
     * 获取可用的下单许可
     */
    private Integer getAvailableOrderTokens(Long goodsId) {
        // 先从本地缓存中获取可用的下单许可
        Integer availableOrderTokens = localCacheService.getIfPresent(goodsId);
        // 本地缓存中存在数据，则直接返回
        if (Objects.nonNull(availableOrderTokens)) {
            return availableOrderTokens;
        }
        // 刷新本地缓存可用的下单许可
        return this.refreshLocalAvailableTokens(goodsId, true);
    }

    /**
     * 刷新本地缓存可用的下单许可，注意DoubleCheck
     */
    private Integer refreshLocalAvailableTokens(Long goodsId, boolean doubleCheck) {
        // 再次校验本地缓存中是否存在数据
        if (doubleCheck) {
            Integer availableOrderTokens = localCacheService.getIfPresent(goodsId);
            if (Objects.isNull(availableOrderTokens)) {
                return availableOrderTokens;
            }
        }
        // 下单许可key
        String availableTokensKey = SeckillConstants.getKey(SeckillConstants.ORDER_TASK_AVAILABLE_TOKENS_KEY, String.valueOf(goodsId));
        // 尝试获取分布式缓存中的下单许可
        Integer latestAvailableOrderTokens = distributedCacheService.getObject(availableTokensKey, Integer.class);
        // 如果分布式缓存中存在下单许可，则设置到本地缓存中，并返回
        if (Objects.nonNull(latestAvailableOrderTokens)) {
            if (lock.tryLock()) {
                try {
                    localCacheService.put(goodsId, latestAvailableOrderTokens);
                } finally {
                    lock.unlock();
                }
            }
            return latestAvailableOrderTokens;
        }
        // 分布式缓存中不存在下单许可，进行刷新分布式缓存的下单许可
        return this.refreshLatestAvailableTokens(goodsId, false);
    }

    /**
     * 刷新分布式缓存的下单许可，double check
     */
    private Integer refreshLatestAvailableTokens(Long goodsId, boolean doubleCheck) {
        String lockKey = SeckillConstants.getKey(SeckillConstants.LOCK_REFRESH_LATEST_AVAILABLE_TOKENS_KEY, String.valueOf(goodsId));
        DistributedLock distributedLock = distributedLockFactory.getDistributedLock(lockKey);
        try {
            // 获取分布式锁
            boolean isLock = distributedLock.tryLock();
            // 分布式锁获取失败，直接返回空
            if (!isLock) {
                return null;
            }
            Integer latestAvailableOrderTokens;
            String availableTokensKey = SeckillConstants.getKey(SeckillConstants.ORDER_TASK_AVAILABLE_TOKENS_KEY, String.valueOf(goodsId));
            if (doubleCheck) {
                // 本地缓存已经存在数据
                Integer availableOrderTokens = localCacheService.getIfPresent(goodsId);
                if (Objects.nonNull(availableOrderTokens)) {
                    return availableOrderTokens;
                }
                // 获取分布式缓存数据
                latestAvailableOrderTokens = distributedCacheService.getObject(availableTokensKey, Integer.class);
                // 分布式缓存中存在数据，设置到本地缓存，并且返回数据
                if (Objects.nonNull(latestAvailableOrderTokens)) {
                    localCacheService.put(goodsId, latestAvailableOrderTokens);
                    return latestAvailableOrderTokens;
                }
            }
            SeckillBusinessCache<Integer> availableStockCache = null;
            // 分桶模式
            if (SeckillConstants.PLACE_ORDER_TYPE_BUCKET.equals(placeOrderType)) {
                availableStockCache = seckillStockDubboService.getAvailableStock(goodsId, 1L);
            } else {
                availableStockCache = seckillGoodsDubboService.getAvailableStock(goodsId, 1L);
            }
            if (availableStockCache == null || !availableStockCache.isExist() || availableStockCache.isRetryLater() || availableStockCache.getData() == null) {
                return null;
            }
            Integer availableStock = availableStockCache.getData();
            // 根据配置的比例计算下单许可
            latestAvailableOrderTokens = (int) Math.ceil(availableStock * multiple);
            distributedCacheService.put(availableTokensKey, latestAvailableOrderTokens, SeckillConstants.ORDER_TASK_EXPIRE_SECONDS, TimeUnit.SECONDS);
            localCacheService.put(goodsId, latestAvailableOrderTokens);
            return latestAvailableOrderTokens;
        } catch (Exception e) {
            log.error("refreshLatestAvailableTokens|刷新下单许可失败:{}", goodsId, e);
        } finally {
            distributedLock.unlock();
        }
        return null;
    }

}
